use std::ptr::NonNull;

/// 对任意内存随机性访问的安全封装，
/// 用户必须定义合适的检查逻辑才允许访问
pub(crate) trait AccessGuard<T> {
    fn before_access(&self, ptr: NonNull<T>);
    fn after_access(&self, _brow: &T) {}
    fn access<N>(&self, ptr: N) -> &mut T
    where
        N: Into<NonNull<T>>,
    {
        let mut ptr = ptr.into();
        self.before_access(ptr);
        let brow = unsafe { ptr.as_mut() };
        self.after_access(brow);
        brow
    }
}

#[cfg(test)]
mod tests {
    use crate::utils::access_guard::AccessGuard;
    use std::ptr::{addr_of_mut, null_mut, NonNull};
    use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};

    struct TestAccessGuard;

    static PTR: AtomicPtr<usize> = AtomicPtr::new(null_mut());
    static VAL: AtomicUsize = AtomicUsize::new(0);
    static CALL_CONST: AtomicUsize = AtomicUsize::new(0);

    impl AccessGuard<usize> for TestAccessGuard {
        fn before_access(&self, ptr: NonNull<usize>) {
            assert_eq!(
                ptr.as_ptr(),
                PTR.load(Ordering::Relaxed),
                "before_access panic"
            );
            CALL_CONST.fetch_add(1, Ordering::Relaxed);
        }

        fn after_access(&self, brow: &usize) {
            assert_eq!(brow, &VAL.load(Ordering::Relaxed), "after_access panic");
            CALL_CONST.fetch_add(1, Ordering::Relaxed);
        }
    }
    #[test]
    fn test_access_guard() {
        CALL_CONST.store(0, Ordering::Relaxed);
        let mut test_value = 16;
        VAL.store(test_value, Ordering::Relaxed);
        PTR.store(addr_of_mut!(test_value), Ordering::Relaxed);
        let guard = TestAccessGuard;
        let access_val = guard.access(NonNull::from(&test_value));
        assert_eq!(access_val, &test_value);
        assert_eq!(*access_val, test_value);
        assert_eq!(CALL_CONST.load(Ordering::Relaxed), 2);
    }

    #[test]
    #[should_panic(expected = "before_access panic")]
    fn before_access_should_panic() {
        CALL_CONST.store(0, Ordering::Relaxed);
        let mut test_value = 16;
        VAL.store(test_value, Ordering::Relaxed);
        // wrong ptr
        PTR.store(
            addr_of_mut!(test_value).wrapping_byte_add(1),
            Ordering::Relaxed,
        );
        let guard = TestAccessGuard;
        guard.access(NonNull::from(&test_value));
    }

    #[test]
    #[should_panic(expected = "after_access panic")]
    fn after_access_should_panic() {
        CALL_CONST.store(0, Ordering::Relaxed);
        let mut test_value = 16;
        // wrong value
        VAL.store(test_value + 1, Ordering::Relaxed);
        PTR.store(addr_of_mut!(test_value), Ordering::Relaxed);
        let guard = TestAccessGuard;
        guard.access(NonNull::from(&test_value));
    }
}
